using System;
using System.IO;
using System.Collections.Generic;

namespace EasyPak
{
	public class PakEntry
    {
        private bool _debug = false;

        private string _FileName;
        public string FileName
        {
            get { return _FileName; }
        }

        private long _offset;
        public long Offset
        {
            get { return _offset; }
        }
        private long _size;
        public long Size
        {
            get { return _size; }
        }
        
        internal byte[] _allFilesData;

        public static PakEntry Read(Stream s, byte[] allFilesData)
        {
            return Read(s, allFilesData, false);
        }

        public static PakEntry Read(Stream s, byte[] allFilesData, bool turnOnDebug)
        {
            PakEntry entry = new PakEntry();
            entry._debug = turnOnDebug;
            entry._allFilesData = allFilesData;

            if (!ReadHeader(s, entry)) return null;

            // Skip the file data as we have the header info (offset and size and filename)
            s.Seek(entry.Size, SeekOrigin.Current);

            return entry;
        }
        
        internal static PakEntry Create(String filename)
        {
            PakEntry entry = new PakEntry();
            entry._FileName = filename;
            return entry;
        }
        

		internal Stream GetStream()
		{
			return new MemoryStream(_allFilesData, (int)_offset, (int)_size, false);
		}
        
        /// <summary>
        /// Writes the input into the output stream.
        /// </summary>
        static int BUFFER_SIZE = 8192;
        public void CopyStream(Stream input, Stream output)
        {
            unchecked
            {
                byte[] buffer = new byte[BUFFER_SIZE];
                int readSize = BUFFER_SIZE;

                int count = input.Read(buffer, 0, readSize);
                while (count > 0)
                {
                    output.Write(buffer, 0, count);
                    count = input.Read(buffer, 0, readSize);
                }
            }
        }

        private void WriteHeader(Stream s, byte[] bytes)
        {
            // write the header info

            int i = 0;

            // Size (Int32)
            Int32 isz = (Int32)_size;
            UInt32 sz = (UInt32)isz;
            bytes[i++] = (byte)(sz & 0x000000FF);
            bytes[i++] = (byte)((sz & 0x0000FF00) >> 8);
            bytes[i++] = (byte)((sz & 0x00FF0000) >> 16);
            bytes[i++] = (byte)((sz & 0xFF000000) >> 24);

            // filename length (Int16)
            Int16 length = (Int16)FileName.Length;
            bytes[i++] = (byte)(length & 0x00FF);
            bytes[i++] = (byte)((length & 0xFF00) >> 8);

            // actual filename
            char[] c = FileName.ToCharArray();
            int j = 0;

            if (_debug)
            {
                System.Console.WriteLine("local header: writing filename, {0} chars", c.Length);
                System.Console.WriteLine("starting offset={0}", i);
            }
            for (j = 0; (j < c.Length) && (i + j < bytes.Length); j++)
            {
                bytes[i + j] = System.BitConverter.GetBytes(c[j])[0];
                if (_debug) System.Console.Write(" {0:X2}", bytes[i + j]);
            }
            if (_debug) System.Console.WriteLine();

            i += j;
            
            if (_debug)
            {
                System.Console.WriteLine("\nAll header data:");
                for (j = 0; j < i; j++)
                    System.Console.Write(" {0:X2}", bytes[j]);
                System.Console.WriteLine();
            }
            // finally, write the header to the stream
            s.Write(bytes, 0, i);
        }



        private static bool ReadHeader(Stream s, PakEntry pe)
        {
            byte[] block = new byte[6];
            int n = s.Read(block, 0, block.Length);
            if (n != block.Length) return false;

            int i = 0;

            pe._size = block[i++] + block[i++] * 256 + block[i++] * 256 * 256 + block[i++] * 256 * 256 * 256;
            
            Int16 filenameLength = (short)(block[i++] + block[i++] * 256);
            
            block = new byte[filenameLength];
            n = s.Read(block, 0, block.Length);
            pe._FileName = StringFromBuffer(block, 0, block.Length);

            // offset of file data is current stream position
            pe._offset = s.Position;

            return true;
        }

        static string StringFromBuffer(byte[] buf, int start, int maxlength)
        {
            int i;
            char[] c = new char[maxlength];
            for (i = 0; (i < maxlength) && (i < buf.Length) && (buf[i] != 0); i++)
            {
                c[i] = (char)buf[i]; // System.BitConverter.ToChar(buf, start+i*2);
            }
            string s = new System.String(c, 0, i);
            return s;
        }

        internal void Write(Stream s)
        {
            byte[] bytes = new byte[4096];
            int n;

            // write the actual file data: 
            Stream fileStream = File.Open(_FileName, FileMode.Open);
            _size = fileStream.Length;

            // write the header:
            WriteHeader(s, bytes);

            if (_debug)
            {
                Console.WriteLine("{0}: writing data to pakfile...", FileName);
                Console.WriteLine("{0}: total data length: {1}", FileName, _size);
            }

            while ((n = fileStream.Read(bytes, 0, bytes.Length)) != 0)
            {
                if (_debug)
                {
                    Console.WriteLine("{0}: transferring {1} bytes...", FileName, n);

                    for (int j = 0; j < n; j += 2)
                    {
                        if ((j > 0) && (j % 40 == 0))
                            System.Console.WriteLine();
                        System.Console.Write(" {0:X2}", bytes[j]);
                        if (j + 1 < n)
                            System.Console.Write("{0:X2}", bytes[j + 1]);
                    }
                    System.Console.WriteLine("\n");
                }

                s.Write(bytes, 0, n);
            }

            fileStream.Close();
        }
    }
}
